import { ArgumentsHost, BadRequestException, Catch, ExceptionFilter, HttpException, Logger } from '@nestjs/common';
import { QueryFailedError } from 'typeorm';

@Catch()
export class ErrorFilter implements ExceptionFilter {
  private readonly logger = new Logger(ErrorFilter.name);
  catch(exception: unknown, host: ArgumentsHost) {
    // 上下文
    const ctx = host.switchToHttp();
    // 响应体
    const response = ctx.getResponse();
    // 请求体
    const request = ctx.getRequest();

    // 报错参数
    const path = ctx.getRequest().url; // 请求路径
    const method = ctx.getRequest().method; // 请求方法
    let statusCode = 500; // 状态码
    let msg = null; // 主动返回的结果信息，一般为自定义消息，显示优先级最高
    let errMsg = 'Internal server error'; // 报错信息，显示优先级大于message
    let message = '服务器故障'; // 描述信息
    let errType = 'unknown'; // 报错名称
    let exceptionResponse = null; // 自定义报错响应体，一般为自定义对象
    let stack = null; // 报错堆栈信息，只做 logger 记录，不做返回体

    // 处理报错信息
    if (exception instanceof BadRequestException) {
      statusCode = exception.getStatus();
      exceptionResponse = exception.getResponse(); // 获取自定义响应体，捕获 DTO 验证错误信息，一般取其中 message 字段
      msg = exceptionResponse.message; // 以自定义响应体中信息为优先级最高
      errMsg = exception.message;
      message = '请求失败';
      errType = exception.name;
      stack = exception.stack;
    } else if (exception instanceof QueryFailedError) {
      /**
       * 捕获情景：
       * 1. 数据库查询报错
       */
      statusCode = 400;
      errMsg = exception.message;
      message = '请求失败';
      errType = exception.name;
      stack = exception.stack;
    } else if (exception instanceof HttpException) {
      /**
       * 捕获情景：
       * 1. 主动 new HttpException() 抛出异常
       * 2. JWT 验证失败报错
       * 3. NotFoundException 系统级 404 报错
       */
      statusCode = exception.getStatus();
      exceptionResponse = exception.getResponse(); // 获取自定义响应体，捕获 new HttpException({...}, code) 的参数
      errMsg = exception.message;
      message = '请求失败';
      errType = exception.name;
      stack = exception.stack;
    } else if (exception instanceof Error) {
      /**
       * 捕获情景：其他报错情况
       */
      errType = exception.name;
      stack = exception.stack;
    }

    // 处理报错结果
    let errRes = {
      path,
      method,
      statusCode,
      msg,
      errMsg,
      message,
      errType,
      timestamp: Date.now()
    };

    // 当有自定义响应体时，合并响应体返回
    if (exceptionResponse && typeof exceptionResponse === 'object') {
      errRes = Object.assign(errRes, exceptionResponse);
    }

    // 日志记录
    this.logger.error(errRes, stack);

    response.status(statusCode).json(errRes);
  }
}
